import {
  CompressedTextureLoader,
  RGBA_PVRTC_2BPPV1_Format,
  RGBA_PVRTC_4BPPV1_Format,
  RGB_PVRTC_2BPPV1_Format,
  RGB_PVRTC_4BPPV1_Format,
} from 'three'

/*
 *	 PVR v2 (legacy) parser
 *   TODO : Add Support for PVR v3 format
 *   TODO : implement loadMipmaps option
 */

class PVRLoader extends CompressedTextureLoader {
  constructor(manager) {
    super(manager)
  }

  parse(buffer, loadMipmaps) {
    const headerLengthInt = 13
    const header = new Uint32Array(buffer, 0, headerLengthInt)

    const pvrDatas = {
      buffer: buffer,
      header: header,
      loadMipmaps: loadMipmaps,
    }

    if (header[0] === 0x03525650) {
      // PVR v3

      return _parseV3(pvrDatas)
    } else if (header[11] === 0x21525650) {
      // PVR v2

      return _parseV2(pvrDatas)
    } else {
      console.error('THREE.PVRLoader: Unknown PVR format.')
    }
  }
}

function _parseV3(pvrDatas) {
  const header = pvrDatas.header
  let bpp, format

  const metaLen = header[12],
    pixelFormat = header[2],
    height = header[6],
    width = header[7],
    // numSurfs = header[ 9 ],
    numFaces = header[10],
    numMipmaps = header[11]

  switch (pixelFormat) {
    case 0: // PVRTC 2bpp RGB
      bpp = 2
      format = RGB_PVRTC_2BPPV1_Format
      break

    case 1: // PVRTC 2bpp RGBA
      bpp = 2
      format = RGBA_PVRTC_2BPPV1_Format
      break

    case 2: // PVRTC 4bpp RGB
      bpp = 4
      format = RGB_PVRTC_4BPPV1_Format
      break

    case 3: // PVRTC 4bpp RGBA
      bpp = 4
      format = RGBA_PVRTC_4BPPV1_Format
      break

    default:
      console.error('THREE.PVRLoader: Unsupported PVR format:', pixelFormat)
  }

  pvrDatas.dataPtr = 52 + metaLen
  pvrDatas.bpp = bpp
  pvrDatas.format = format
  pvrDatas.width = width
  pvrDatas.height = height
  pvrDatas.numSurfaces = numFaces
  pvrDatas.numMipmaps = numMipmaps
  pvrDatas.isCubemap = numFaces === 6

  return _extract(pvrDatas)
}

function _parseV2(pvrDatas) {
  const header = pvrDatas.header

  const headerLength = header[0],
    height = header[1],
    width = header[2],
    numMipmaps = header[3],
    flags = header[4],
    // dataLength = header[ 5 ],
    // bpp =  header[ 6 ],
    // bitmaskRed = header[ 7 ],
    // bitmaskGreen = header[ 8 ],
    // bitmaskBlue = header[ 9 ],
    bitmaskAlpha = header[10],
    // pvrTag = header[ 11 ],
    numSurfs = header[12]

  const TYPE_MASK = 0xff
  const PVRTC_2 = 24,
    PVRTC_4 = 25

  const formatFlags = flags & TYPE_MASK

  let bpp, format
  const _hasAlpha = bitmaskAlpha > 0

  if (formatFlags === PVRTC_4) {
    format = _hasAlpha ? RGBA_PVRTC_4BPPV1_Format : RGB_PVRTC_4BPPV1_Format
    bpp = 4
  } else if (formatFlags === PVRTC_2) {
    format = _hasAlpha ? RGBA_PVRTC_2BPPV1_Format : RGB_PVRTC_2BPPV1_Format
    bpp = 2
  } else {
    console.error('THREE.PVRLoader: Unknown PVR format:', formatFlags)
  }

  pvrDatas.dataPtr = headerLength
  pvrDatas.bpp = bpp
  pvrDatas.format = format
  pvrDatas.width = width
  pvrDatas.height = height
  pvrDatas.numSurfaces = numSurfs
  pvrDatas.numMipmaps = numMipmaps + 1

  // guess cubemap type seems tricky in v2
  // it juste a pvr containing 6 surface (no explicit cubemap type)
  pvrDatas.isCubemap = numSurfs === 6

  return _extract(pvrDatas)
}

function _extract(pvrDatas) {
  const pvr = {
    mipmaps: [],
    width: pvrDatas.width,
    height: pvrDatas.height,
    format: pvrDatas.format,
    mipmapCount: pvrDatas.numMipmaps,
    isCubemap: pvrDatas.isCubemap,
  }

  const buffer = pvrDatas.buffer

  let dataOffset = pvrDatas.dataPtr,
    dataSize = 0,
    blockSize = 0,
    blockWidth = 0,
    blockHeight = 0,
    widthBlocks = 0,
    heightBlocks = 0

  const bpp = pvrDatas.bpp,
    numSurfs = pvrDatas.numSurfaces

  if (bpp === 2) {
    blockWidth = 8
    blockHeight = 4
  } else {
    blockWidth = 4
    blockHeight = 4
  }

  blockSize = (blockWidth * blockHeight * bpp) / 8

  pvr.mipmaps.length = pvrDatas.numMipmaps * numSurfs

  let mipLevel = 0

  while (mipLevel < pvrDatas.numMipmaps) {
    const sWidth = pvrDatas.width >> mipLevel,
      sHeight = pvrDatas.height >> mipLevel

    widthBlocks = sWidth / blockWidth
    heightBlocks = sHeight / blockHeight

    // Clamp to minimum number of blocks
    if (widthBlocks < 2) widthBlocks = 2
    if (heightBlocks < 2) heightBlocks = 2

    dataSize = widthBlocks * heightBlocks * blockSize

    for (let surfIndex = 0; surfIndex < numSurfs; surfIndex++) {
      const byteArray = new Uint8Array(buffer, dataOffset, dataSize)

      const mipmap = {
        data: byteArray,
        width: sWidth,
        height: sHeight,
      }

      pvr.mipmaps[surfIndex * pvrDatas.numMipmaps + mipLevel] = mipmap

      dataOffset += dataSize
    }

    mipLevel++
  }

  return pvr
}

export { PVRLoader }
